defmodule MapLib do
	@moduledoc false
	import ErrCode

	# 可以用merge替代, 后面删除
	def replace(map, replace) do
		Map.merge(map, replace)
	end

	@spec from_keyword([{any, any}]) :: map
	def from_keyword(keyword) do
		Enum.reduce(
			keyword,
			%{},
			fn ({key, value}, acc) ->
				Map.put(acc, key, value)
			end
		)
	end

	# 可以用 drop 替代
	def drop_key(map, keys) do
		Map.drop(map, keys)
	end

	def drop_key(list, keys, mod) when is_list(list) do
		list
		|> Enum.reduce(
			   [],
			   fn (h, acc) ->
				   [mod.drop_key(h, keys) | acc]
			   end
		   )
	end

	# map => new_map with keys
	def fetch_key(map, keys) do
		Map.take(map, keys)
	end

	def fetch(map, keys, to_jsons) do
		Enum.reduce(
			keys,
			%{},
			fn (key, acc) ->
				case Enum.member?(to_jsons, key) do
					true ->
						Map.put(acc, key, Jason.decode!(Map.get(map, key, "{}")))
					_ ->
						Map.put(acc, key, Map.get(map, key))
				end
			end
		)
	end

	@spec get_by_key([map], any) :: [any]
	def get_by_key(nil, _) do
		[]
	end
	def get_by_key(list, key) do
		Enum.reduce(
			list,
			[],
			fn
				(nil, acc) ->
					acc
				(map, acc) ->
					value = Map.get(map, key)
					if !(value in acc) do
						[value | acc]
					else
						acc
					end
			end
		)
	end

	@spec get_in(map, [any], any) :: any
	def get_in(map, keys, default \\ nil) when is_map(map) do
		case Kernel.get_in(map, keys) do
			nil ->
				default
			v ->
				v
		end
	end

	def put_in(map, [h | keys], value) do
		cur = Map.get(map, h, %{})
		v = put_in(keys, h, cur, value)
		Map.merge(map, v)
	end

	defp put_in([], last_key, last_value, value) do
		Map.put(last_value, last_key, value)
	end
	defp put_in([h | keys], last_key, last_value, value) do
		is_map? = is_map(last_value)
		cur = if is_map? do
			Map.get(last_value, h, %{h => %{}})
		else
			%{h => %{}}
		end
		v = put_in(keys, h, cur, value)
		if is_map? do
			Map.put(last_value, last_key, v)
		else
			%{last_key => v}
		end
	end

	@spec elem_fetch_keys([map], [any]) :: [map]
	def elem_fetch_keys(list, keys) do
		Enum.reduce(
			list,
			[],
			fn
				(nil, acc) ->
					acc
				(map, acc) ->
					[MapLib.fetch_key(map, keys) | acc]
			end
		)
	end

	@spec zip([any], [any], map) :: map
	def zip([], [], map) do
		map
	end
	def zip([h1 | list1], [h2 | list2], map) do
		zip(list1, list2, Map.put(map, h1, h2))
	end


	@doc """
	constant 中是否有任何一个在checks里面
	"""
	def any_exists!(constant, checks) do
		Enum.any?(
			constant,
			fn (h) ->
				if h in checks do
					throw({:error, {input_error(), h}})
				else
					true
				end
			end
		)
	end
	@doc """
	trusted: 数据库里的
	outer: 被检查的
	"""
	def any_exists?(outer, trusted) do
		Enum.any?(
			outer,
			fn (h) ->
				Enum.member?(trusted, h)
			end
		)
	end

	@doc """
	trusted: 数据库里的
	outer: 被检查的
	"""
	def all_exists(outer, trusted) do
		Enum.all?(
			outer,
			fn
				({key, _}) ->
					key in trusted
				(h) ->
					Enum.member?(trusted, h)
			end
		)
	end

	# {key组成的map, 剩余的map}
	@spec pop(map, [any]) :: {map, map}
	def pop(enum, keys) do
		Enum.reduce(
			keys,
			{%{}, enum},
			fn
				{key, default}, {acc1, acc2} ->
					{value, rest} = Map.pop(acc2, key, default)
					{Map.put(acc1, key, value), rest}
				key, {acc1, acc2} ->
					{value, rest} = Map.pop(acc2, key, nil)
					{Map.put(acc1, key, value), rest}
			end
		)
	end

	# [%{uid1 => xx, uid2 => xx}], key 为 uid ===> %{uid1 => xx, uid2 => xx}
	def array2map(list, key) do
		Enum.reduce(
			list,
			%{},
			fn (row, acc) ->
				sub_key = if is_list(key) do
					for k <- key do
						Map.get(row, k)
					end
					|> List.to_tuple()
				else
					Map.get(row, key)
				end
				Map.put(acc, sub_key, row)
			end
		)
	end

	# [{key, value}]
	def list_to_map(list) do
		list
		|> Enum.reduce(
			   %{},
			   fn ({key, value}, acc) ->
				   Map.put(acc, key, value)
			   end
		   )
	end

	def set_struct(enum, mod) do
		enum
		|> Map.put(:__struct__, mod)
	end

	@spec only_exists_keys(map, list) :: true | false
	def only_exists_keys(enums, keys) do
		Enum.reduce_while(
			enums,
			false,
			fn (enum, _acc) ->
				cur_keys = Map.keys(enum)
				if cur_keys -- keys == [] && keys -- cur_keys == [] do
					{:cont, true}
				else
					{:halt, false}
				end
			end
		)
	end

	def keyfind(list, key, value, default) do
		keyfind(list, %{key => value}, default)
	end
	def keyfind(list, check_map, default) do
		Enum.reduce_while(
			list,
			default,
			fn (ele, acc) ->
				Enum.reduce_while(
					check_map,
					false,
					fn ({key, value}, _acc) ->
						if Map.get(ele, key) == value do
							{:cont, true}
						else
							{:halt, false}
						end
					end
				)
				|> case do
					   true ->
						   {:halt, ele}
					   false ->
						   {:cont, acc}

				   end
			end
		)
	end

	#[map]
	def keytake(list, key, value, default) do
		keytake(list, %{key => value}, default)
	end
	def keytake([], _check_map, default) do
		{default, []}
	end
	def keytake(list, check_map, default) do
		Enum.reduce(
			list,
			{default, []},
			fn (ele, {v, acc}) ->
				Enum.reduce_while(
					check_map,
					false,
					fn ({key, value}, _acc) ->
						if Map.get(ele, key) == value do
							{:cont, true}
						else
							{:halt, false}
						end
					end
				)
				|> case do
					   true ->
						   {ele, acc}
					   false ->
						   {v, [ele | acc]}
				   end
			end
		)
	end


	def keyreplace(list, key, v, value) do
		Enum.map(
			list,
			fn (ele) ->
				if Map.get(ele, key) == v do
					ele
				else
					value
				end
			end
		)
	end


end
